/*
Copyright (c) 2011, Felipe Michels Fontoura
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
    * Redistributions of source code must retain the above copyright
      notice, this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright
      notice, this list of conditions and the following disclaimer in the
      documentation and/or other materials provided with the distribution.
    * Neither the name of Felipe Michels Fontoura nor the names of his
      contributors may be used to endorse or promote products derived from
      this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL FELIPE MICHELS FONTOURA BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

import gui.Screen;

import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

import javax.imageio.ImageIO;
import javax.swing.JFrame;

import jason.asSyntax.*;
import jason.environment.*;

/**
 * Ambiente dos aspiradores formando coalizão.
 * @author Felipe Michels Fontoura
 */
public class BotsEnv extends TimeSteppedEnvironment implements Constants {
	/**
	 * Largura da grade.
	 */
	private static final int width = 10;

	/**
	 * Altura da grade.
	 */
	private static final int height = 10;

	/**
	 * Vetor de robôs.
	 */
	private RoboLimpador[] limpadores;

	/**
	 * Vetor de robôs.
	 */
	private RoboSugismundo[] sugismundos;

	/**
	 * Matriz de lixos.
	 */
	private Lixo[][] lixo;

	/**
	 * Matriz de lixos.
	 */
	private Oleo[][] oleo;

	/**
	 * Objeto de interface gráfica.
	 */
	private GUI gui;

	/**
	 * Método chamado antes do início da execução do sistema multi-agente.
	 */
	@Override public void init(String[] args) {
		super.init(args);
		
		// cria o vetor de robôs.
		limpadores = new RoboLimpador[5];
		for (int i = 0; i < limpadores.length; i ++) {
			RoboLimpador limpador = new RoboLimpador(ID_LIMPADOR, i + 1);
			limpador.x = (int)(width * Math.random());
			limpador.y = (int)(height * Math.random());
			limpador.sabeVarrer   = true;
			limpador.sabeSegurar  = true;
			limpador.sabeLevar    = true;
			limpador.sabeAspergir = true;
			limpador.sabePassar   = true;
			limpadores[i] = limpador;
		}

		// define as habilidades.
		limpadores[0].sabeVarrer   = true;
		limpadores[0].sabePassar   = true;
		limpadores[1].sabeSegurar  = true;
		limpadores[1].sabeLevar    = true;
		limpadores[2].sabeAspergir = true;
		limpadores[3].sabeVarrer   = true;
		limpadores[3].sabeSegurar  = true;
		limpadores[4].sabeVarrer   = true;

		sugismundos = new RoboSugismundo[2];
		for (int i = 0; i < sugismundos.length; i ++) {
			RoboSugismundo sugismundo = new RoboSugismundo(i + 1);
			sugismundo.x = (int)(width  * Math.random());
			sugismundo.y = (int)(height * Math.random());
			sugismundos[i] = sugismundo;
		}
		
		// cria os lixos.
		lixo = new Lixo[width][height];
		for (int x = 0; x < width; x ++) for (int y = 0; y < height; y ++)
			lixo[x][y] = null;
		lixo[(int)(width * Math.random())][(int)(height * Math.random())] = new Lixo();
		lixo[(int)(width * Math.random())][(int)(height * Math.random())] = new Lixo();

		// define o intervalo entre turnos (em ms).
		setSleep(100);

		// cria e configura a interface gráfica.
		gui = new GUI();
		gui.setVisible(true);
		JFrame frame = ((JFrame)gui.getFrame());
		frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);

		// atualiza as percepções dos agentes pela primeira vez.
		updateAgsPercept();
	}

	/**
	 * Método chamado antes do término da execução do sistema multi-agente.
	 */
	@Override public void stop() {
		super.stop();

		// destrói a interface gráfica.
		gui.dispose();
	}

	/**
	 * Método chamado toda a vez que um agente vai executar uma ação.
	 */
	@Override public boolean executeAction(String agentName, Structure action) {
		// verifica qual robô está selecionado.
		RoboLimpador limpador = null;
		for (int i = 0; i < limpadores.length; i ++)
			if (limpadores[i].id.equals(agentName))
				limpador = limpadores[i];
		if (limpador != null) {
			if ("segurarPa".equals(action.getFunctor())) {
				if (lixo[limpador.x][limpador.y] != null)
				if (! lixo[limpador.x][limpador.y].segurado) {
					lixo[limpador.x][limpador.y].segurado = true;
					System.out.println("Segurando...");
					return true;
				}
			}

			if ("varrerPoeira".equals(action.getFunctor())) {
				if (lixo[limpador.x][limpador.y] != null)
				if (lixo[limpador.x][limpador.y].segurado && ! lixo[limpador.x][limpador.y].varrido) {
					lixo[limpador.x][limpador.y].varrido = true;
					System.out.println("Varrendo...");
					return true;
				}
			}

			if ("carregarLixo".equals(action.getFunctor())) {
				if (lixo[limpador.x][limpador.y] != null)
				if (lixo[limpador.x][limpador.y].segurado && lixo[limpador.x][limpador.y].varrido) {
					lixo[limpador.x][limpador.y] = null;
					System.out.println("Levando...");
					return true;
				}
			}

			if ("olharPara".equals(action.getFunctor())) {
				/* procura a direção */
				for (int i = 0; i < DIRECTION_NAME.length; i ++)
					if (action.getTerm(0).toString().equals(DIRECTION_NAME[i])) {
						limpador.direcao = i;
						return true;
					}
			}

			if ("darPasso".equals(action.getFunctor())) {
				if (limpador.direcao == DIRECTION_UP) if (limpador.y > 0) {
					limpador.y --;
					return true;
				}
				if (limpador.direcao == DIRECTION_DOWN) if (limpador.y < (height - 1)) {
					limpador.y ++;
					return true;
				}
				if (limpador.direcao == DIRECTION_LEFT) if (limpador.x > 0) {
					limpador.x --;
					return true;
				}
				if (limpador.direcao == DIRECTION_RIGHT) if (limpador.x < (width - 1)) {
					limpador.x ++;
					return true;
				}
			}

			if ("nop".equals(action.getFunctor())) {
				return true;
			}
		}

		RoboSugismundo sugismundo = null;
		for (int i = 0; i < sugismundos.length; i ++)
			if (sugismundos[i].id.equals(agentName))
				sugismundo = sugismundos[i];
		if (sugismundo != null) {
			if ("escolherAlvo".equals(action.getFunctor())) {
				sugismundo.aleatorioX = (int)(width * Math.random());
				sugismundo.aleatorioY = (int)(width * Math.random());
				sugismundo.poeira = true;  // TODO aqui
				return true;
			}
			
			if ("jogarPoeira".equals(action.getFunctor())) {
				lixo[sugismundo.x][sugismundo.y] = new Lixo();
				return true;
			}

			if ("olharPara".equals(action.getFunctor())) {
				/* procura a direção */
				for (int i = 0; i < DIRECTION_NAME.length; i ++)
					if (action.getTerm(0).toString().equals(DIRECTION_NAME[i])) {
						sugismundo.direcao = i;
						return true;
					}
			}

			if ("darPasso".equals(action.getFunctor())) {
				if (sugismundo.direcao == DIRECTION_UP) if (sugismundo.y > 0) {
					sugismundo.y --;
					return true;
				}
				if (sugismundo.direcao == DIRECTION_DOWN) if (sugismundo.y < (height - 1)) {
					sugismundo.y ++;
					return true;
				}
				if (sugismundo.direcao == DIRECTION_LEFT) if (sugismundo.x > 0) {
					sugismundo.x --;
					return true;
				}
				if (sugismundo.direcao == DIRECTION_RIGHT) if (sugismundo.x < (width - 1)) {
					sugismundo.x ++;
					return true;
				}
			}

			if ("nop".equals(action.getFunctor())) {
				return true;
			}
		}

		/* ação é do orgManager */
		if (agentName.equals(ORG_MANAGER)) {
			if ("nop".equals(action.getFunctor())) {
				return true;
			}
		}

		// não executou ação alguma... o agente ou a ação são inválidos!
		return false;
	}

	/**
	 * Método chamado para ver a duração, em turnos, de uma ação.
	 */
	@Override protected int requiredStepsForAction(String agentName, Structure action) {
		RoboSugismundo sugismundo = null;
		for (int i = 0; i < sugismundos.length; i ++)
			if (sugismundos[i].id.equals(agentName))
				return 5;

		// as ações levam 1 turno.
		return 2;
	}

	/**
	 * Método chamado para atualizar as percepções dos agentes, após cada turno.
	 */
	@Override protected void updateAgsPercept() {
		super.updateAgsPercept();

		// remove todas as percepções dos agentes.
		clearAllPercepts();

		// adiciona as percepções individuais.
		for (int i = 0; i < sugismundos.length; i ++) {
			RoboSugismundo sugismundo = sugismundos[i];

			// percepções referentes ao robô.
			addPercept(sugismundo.id, Literal.parseLiteral("estouEm(" + sugismundo.x + "," + sugismundo.y + ")"));
			addPercept(sugismundo.id, Literal.parseLiteral("olhandoPara(" + DIRECTION_NAME[sugismundo.direcao] + ")"));
			addPercept(sugismundo.id, Literal.parseLiteral("alvo(" + sugismundo.aleatorioX + "," + sugismundo.aleatorioY + ")"));
			if (sugismundo.poeira)
				addPercept(sugismundo.id, Literal.parseLiteral("queroJogar(poeira)"));
			else
				addPercept(sugismundo.id, Literal.parseLiteral("queroJogar(oleo)"));
		}

		// adiciona as percepções individuais.
		for (int i = 0; i < limpadores.length; i ++) {
			RoboLimpador limpador = limpadores[i];

			// percepções referentes ao robô.
			addPercept(limpador.id, Literal.parseLiteral("estouEm(" + limpador.x + "," + limpador.y + ")"));
			addPercept(limpador.id, Literal.parseLiteral("olhandoPara(" + DIRECTION_NAME[limpador.direcao] + ")"));
			if (limpador.sabeLevar)
				addPercept(limpador.id, Literal.parseLiteral("sei(carregarLixo)"));
			if (limpador.sabeVarrer)
				addPercept(limpador.id, Literal.parseLiteral("sei(varrerPoeira)"));
			if (limpador.sabeSegurar)
				addPercept(limpador.id, Literal.parseLiteral("sei(segurarPa)"));
			if (limpador.sabeAspergir)
				addPercept(limpador.id, Literal.parseLiteral("sei(aspergir)"));
			if (limpador.sabePassar)
				addPercept(limpador.id, Literal.parseLiteral("sei(passar)"));
		}

		// adiciona as percepções coletivas.
		for (int i = 0; i < limpadores.length; i ++) {
			RoboLimpador limpador = limpadores[i];
			addPercept(Literal.parseLiteral("estaEm(" + limpador.id + "," + limpador.x + "," + limpador.y + ")"));
		}
		for (int x = 0; x < width; x ++) for (int y = 0; y < height; y ++) {
			if (lixo[x][y] != null)
				addPercept(Literal.parseLiteral("lixo(" + x + "," + y + ")"));
		}

		// assim que atualizou as percepções, atualiza a interface gráfica.
		gui.redraw();
	}

	/**
	 * Interface gráfica do ambiente.
	 * @author Felipe Michels Fontoura
	 */
	class GUI extends Screen {
		/**
		 * Imagem com uma célula da grade.
		 */
		private BufferedImage img_cell;

		/**
		 * Imagem com um robo.
		 */
		private BufferedImage img_bot;

		/**
		 * Imagem com um robo.
		 */
		private BufferedImage img_green;

		/** 
		 * Imagem com a sujeira.
		 */
		private BufferedImage img_dirt;

		/**
		 * Imagem com as flechas, indexadas pelas constantes de direção.
		 */
		private BufferedImage arrows[];

		/**
		 * Construtor.
		 */
		public GUI() {
			/* construtor da superclasse */
			super("Vacuum cleaner", 600, 600);

			/* abre as imagens */
			try {
				img_cell = ImageIO.read(new File("img" + File.separator + "cell.png"));
				img_bot  = ImageIO.read(new File("img" + File.separator + "bot.png"));
				img_dirt = ImageIO.read(new File("img" + File.separator + "dirt.png"));
				img_green = ImageIO.read(new File("img" + File.separator + "green-bot.png"));
				arrows   = new BufferedImage[4];
				for (int i = 0; i < 4; i ++)
					arrows[i] = ImageIO.read(new File("img" + File.separator + "flecha_" + DIRECTION_NAME[i]+ ".png"));
			} catch (IOException e) {
				e.printStackTrace();
			}
		}

		/**
		 * Implementação do desenho da tela.
		 */
		@Override public void draw(Graphics g) {
			BufferedImage image = new BufferedImage(width*150, height*150, BufferedImage.TYPE_4BYTE_ABGR);
			Graphics2D g2d = image.createGraphics();

			// desenha a grade.
			for (int x = 0; x < width; x ++) for (int y = 0; y < height; y ++)
				g2d.drawImage(img_cell, 150 * x, 150 * y, null);

			// desenha o lixo.
			for (int x = 0; x < width; x ++) for (int y = 0; y < height; y ++)
				if (lixo[x][y] != null)
				g2d.drawImage(img_dirt, x * 150, y * 150, null);

			// desenha os robôs não-selecionados.
			int bot_shift = -(2*limpadores.length + 2*sugismundos.length);
			for (int i = 0; i < sugismundos.length; i ++) {
				RoboSugismundo bot = sugismundos[i];
				bot_shift += 4;
				g2d.drawImage(img_green, bot.x*150 + bot_shift, bot.y*150 + bot_shift, null);
				g2d.drawImage(arrows[bot.direcao], bot.x*150 + bot_shift, bot.y*150 + bot_shift, null);
			}
			for (int i = 0; i < limpadores.length; i ++) {
				RoboLimpador bot = limpadores[i];
				bot_shift += 4;
				g2d.drawImage(img_bot, bot.x*150 + bot_shift, bot.y*150 + bot_shift, null);
				g2d.drawImage(arrows[bot.direcao], bot.x*150 + bot_shift, bot.y*150 + bot_shift, null);
			}

			// usa interpolação para dedimensionar a imagem... fica visualmente bom.
			g2d = (Graphics2D)g;
			g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
			g2d.drawImage(image, 0, 0, getWidth(), getHeight(), null);
		}
	}
}
